
package com.univcasa.managing.util;
    
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NoPermissionException;
import javax.naming.OperationNotSupportedException;
import javax.naming.PartialResultException;
import javax.naming.directory.Attribute;
import javax.naming.directory.AttributeModificationException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import com.univcasa.managing.util.exception.AuthLDAPException;
import com.univcasa.managing.util.exception.LDAPException;
import com.univcasa.managing.util.exception.SearchLDAPException;
import com.univcasa.managing.util.exception.WriteLDAPException;
import org.apache.log4j.Logger;


public class LDAPHandler {


        private static Logger log = Logger.getLogger(LDAPHandler.class);
        private String[] attributes = null;
	private Hashtable attributesMultiValues = null;
	private int maxEntries = 0;
	private String base = null;
	private String filter = null;
	private int scope = 0;
	private String url = null;
	private String bindDn = null;
	private String bindPass = null;
	private DirContext  ctx = null;
	private Hashtable env = null;
	private final static String LDAP_USER_PASSWORD = "userPassword";

	/**
	 * Constructeur
	 */
	public LDAPHandler() {
	}

	/**
	 * Définit les attributs à rechercher ou a écrire
	 * @param listAttr
	 */
	public void setAttributes(String[] listAttr) {
		attributes = listAttr;
	}
	public void setAttributes(Vector listAttr) {
		attributes = (String[])listAttr.toArray(new String[listAttr.size()]);
	}
        
	/**
	 * Définit les valeurs des attributs à enregistrer
	 * @param listValues
	 */
	public void setAttributesValues(Hashtable listValues) {
		attributesMultiValues = listValues;
	}

	/**
	 * Définit le nombre maximun de réponse que peut renvoyer la méthode de recherche
	 * 0 indique un nombre illimité de réponses
	 * @param max
	 */
	public void setMaxEntries(int max) {
		maxEntries = max;
	}

	/**
	 * Définit la base de recherche
	 * @param b
	 */
	public void setBase(String b) {
		base = b;
	}

	/**
	 * Définit le filtre de recherche
	 * @param f
	 */
	public void setFilter(String f) {
		filter = f;
	}

	/**
	 * Définit la profondeur de recherche (sub ou one-level)
	 * @param s
	 */
	public void setScope(int s) {
		scope = s;
	}

	/**
	 * Définit l'url de connection
	 * @param u
	 */
	public void setUrl(String u) {
		url = u;
	}

	/**
	 * Définit le DN de connection
	 * @param u
	 */
	public void setBindDn(String u) {
		bindDn = u;
	}

	/**
	 * Définit le mo de connection
	 * @param u
	 */
	public void setBindPass(String u) {
		bindPass = u;
	}

        public void searchResults(){

            try {
            NamingEnumeration results = search();
            while (results != null && results.hasMore()) {
				//Lecture d'une entrée
				SearchResult entry = (SearchResult) results.next();
                                System.out.println("entry: " + entry.getName() );
                                //les attributs de l'entrée
				Attributes attrs = entry.getAttributes();
				if (attrs == null) {
					// aucun attributs associés à ce DN...
					log.warn("aucun attributs associés à ce DN");
				} else {
					//Parcours de tous les attributs
					for (NamingEnumeration attEnum = attrs.getAll();
					attEnum.hasMoreElements();)
                                        {
                                            Attribute attr = (Attribute) attEnum.next();
                                            String attrId = attr.getID();
                                            System.out.println( attrId);
                                            for(Enumeration vals = attr.getAll(); vals.hasMoreElements();){
                                                String val = (String) vals.nextElement();
                                                System.out.println( val );
                                            }
					}
				}
			}
        } catch (LDAPException ex) {
            log.error("error", ex);
        } catch (NamingException nex) {
            log.error("error", nex);
        }

        }

	/**
	 * Lance la recherche
	 * @return L'énumération des résultats
	 * @throws LDAPException (SearchLDAPException)
	 */
	public NamingEnumeration search() throws LDAPException {
		NamingEnumeration answer = null;

		if (true){

			/*Criteres de recherche*/
			SearchControls sc = new SearchControls();
			sc.setReturningAttributes(attributes);
			sc.setCountLimit(maxEntries);
			sc.setSearchScope(scope);
                        //sc.setSearchScope(SearchControls.SUBTREE_SCOPE);

                        /* Second method of Searchning:
                        Attributes matchAttrs = new BasicAttributes(true);
                        matchAttrs.put(new BasicAttribute("uid", "kevink"));
                         * */

			/*Lance la recherche*/
			try {
				answer = ctx.search(base, filter, sc);
                                //Second method execution:
                                //answer = ctx.search("ou=People,o=myserver.com",matchAttrs);
			} catch (NamingException e) {
			    log.error("LDAPHandler:search() : " ,e);
				throw new SearchLDAPException();
			}
			}

		return (answer);
	}


	/**
	 * Ferme la connection
	 */
	public void close() {
		if (ctx!=null){
			try {
				ctx.close();
				ctx = null;
			} catch (NamingException e) {
			    log.error("LDAPHandler:close() : " ,e);
			}
		}
	}


	/**
	 * Ouvre la connection. Si le BindDN est renseigné, c'est une connection non anonyme.
	 * @return True si la connection est ouverte.
	 * @throws LDAPException (AuthLDAPException)
	 */
	public boolean authenticate() throws LDAPException{
		String dnLog = new String ();
		dnLog = "anonymous";

		env = new Hashtable();
		env.put(
			InitialDirContext.INITIAL_CONTEXT_FACTORY,
			"com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, url);

		if ((bindDn != null) && (!bindDn.equals("")) ){
			env.put(InitialDirContext.SECURITY_AUTHENTICATION, "simple");
			env.put(InitialDirContext.SECURITY_PRINCIPAL,      bindDn);
			env.put(InitialDirContext.SECURITY_CREDENTIALS,    bindPass);
			dnLog = bindDn;
		}

		try {
			ctx = new InitialDirContext(env);
		} catch (NamingException e1) {
		    log.error("LDAPHandler:authenticate() : " + dnLog +" authentification failed");
			throw new AuthLDAPException("L'authentification de "+dnLog+" a échoué.");
		}

		if (log.isDebugEnabled()){
		    log.debug("LDAPHandler:authenticate() : " + dnLog +" authenticated");
		}
		return true;
	}



	/**
	 * Ecrit dans LDAP l'objet dont le DN est en paramètre.
	 * L'écriture ne concerne que les attributs définis.
	 * @param shortDn
	 * @throws LDAPException (SearchLDAPException,WriteLDAPException)
	 */
	public void write(String shortDn) throws LDAPException{
		if (authenticate()){
			Attributes a = null;

			try {
				a = ctx.getAttributes(shortDn,attributes) ;
			} catch (NamingException e1) {
			    log.error("LDAPHandler:write() : NamingException : ",e1);
				throw new SearchLDAPException("erreur dans l'acquisition d'un attribut.");
			}

			/*Si l'entrée existe et a retourné au moins un des attributs demandés*/
			if (a!=null){
				log.info("LDAPHandler:write() : Preparation de modification de "+shortDn+" ");
				/*Parcours de la liste des attributs demandés*/
				for (int i=0;i<attributes.length;i++){
					log.info("LDAPHandler:write() : * modification de : "+attributes[i]+" ");
					/*Suppression de l'attribut dans le LDAP*/
					if (!attributes[i].equals(LDAP_USER_PASSWORD)){
						if (a.get(attributes[i])!=null){
		    		    	boolean increase = true;
		    		    	while (increase){
		    		    		try {
		    		    			log.info("LDAPHandler:write() :          - Ancienne valeur : "+(String)a.get(attributes[i]).get(0)+" ");
		    	    		    	a.get(attributes[i]).remove(0);
		    		    		}
			    		    	catch(ArrayIndexOutOfBoundsException e){
			    		    		increase=false;
			    		    	}catch (NamingException e) {
			    		    		increase=false;
								}
		    		    	}
						}
					}

					/*Parcours de la liste des valeurs de l'attribut*/
					String[] vals = (String[])attributesMultiValues.get(attributes[i]);
					boolean aucuneValeur = true;
					if ( (vals!=null) && (vals.length>0) ){
						boolean created = false;
						/*Parcours des valeurs de l'attribut*/
				    	for (int j =0;j<vals.length;j++){
				    		/*Il ne faut pas ajouter des valeurs vides (sinon plantage)*/
				    		if (!vals[j].equals("")){
				    			aucuneValeur = false;
				    		    if (!created){
				    		    	/*Création de l'attribut*/
					    			a.put(attributes[i],vals[j]);
					    			created=true;
					    		}
					    		else{
					    			/*Ajout d'une valeur*/
					    			a.get(attributes[i]).add(vals[j]);
					    		}
				    		    if (!attributes[i].equals(LDAP_USER_PASSWORD))
				    		    	log.info("LDAPHandler:write() :          + Nouvelle valeur : "+vals[j]);
				    		}

				    	}
					}
					if (aucuneValeur){
						log.info("LDAPHandler:write() :          + Mise à vide");
					}
				}

				try {

					log.info("LDAPHandler:write() : tentative d'enregistrement ...");

					ctx.modifyAttributes(shortDn,DirContext.REPLACE_ATTRIBUTE,a);

					log.info("LDAPHandler:write() : enregistrement effectue.");
	            }
	            catch(NoPermissionException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => NoPermissionException : ",e);
					throw new WriteLDAPException("Vous n'avez pas les droits pour modifier l'un des attributs.");
	            }
	            catch (AttributeModificationException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => AttributeModificationException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
				}
				catch (OperationNotSupportedException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => OperationNotSupportedException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
				}
				catch (PartialResultException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => PartialResultException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
					}
				catch (NamingException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => NamingException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
				}
			}
			else{
			    log.error("LDAPHandler:write() : impossible de trouver l'entree LDAP");
				throw new SearchLDAPException("Erreur dans l'acquisition d'un attribut.");
			}
		}
	}
        
        public void updateDirecory(NamingEnumeration results) throws LDAPException,NamingException {
            
            if (authenticate()){

                        while (results != null && results.hasMore()) {

                            Attributes a = null ;
                            SearchResult entry = (SearchResult) results.next();
                            String entryName = entry.getName() ;
                            String shortDn =  entryName + "," +base ;

                            String[] attr_values = new String[]{ entryName.replace("uid=", "")+"@etude.univcasa.ma"};
                            Hashtable attr_hash = new Hashtable();

                            attr_hash.put("mail",  attr_values );
                            setAttributesValues( attr_hash );

			try {
				a = ctx.getAttributes(shortDn,attributes);
			} catch (NamingException e1) {
			    log.error("LDAPHandler:write() : NamingException : ",e1);
				throw new SearchLDAPException("erreur dans l'acquisition d'un attribut.");
			}

			/*Si l'entrée existe et a retourné au moins un des attributs demandés*/
			if (a!=null){
				log.info("LDAPHandler:write() : Preparation de modification de "+shortDn+" ");
				/*Parcours de la liste des attributs demandés*/
				for (int i=0;i<attributes.length;i++){
					log.info("LDAPHandler:write() : * modification de : "+attributes[i]+" ");
					/*Suppression de l'attribut dans le LDAP*/
					if (!attributes[i].equals(LDAP_USER_PASSWORD)){
						if (a.get(attributes[i])!=null){
		    		    	boolean increase = true;
		    		    	while (increase){
		    		    		try {
		    		    			log.info("LDAPHandler:write() :          - Ancienne valeur : "+(String)a.get(attributes[i]).get(0)+" ");
		    	    		    	a.get(attributes[i]).remove(0);
		    		    		}
			    		    	catch(ArrayIndexOutOfBoundsException e){
			    		    		increase=false;
			    		    	}catch (NamingException e) {
			    		    		increase=false;
								}
		    		    	}
						}
					}

					/*Parcours de la liste des valeurs de l'attribut*/
					String[] vals = (String[])attributesMultiValues.get(attributes[i]);
					boolean aucuneValeur = true;
					if ( (vals!=null) && (vals.length>0) ){
						boolean created = false;
						/*Parcours des valeurs de l'attribut*/
				    	for (int j =0;j<vals.length;j++){
				    		/*Il ne faut pas ajouter des valeurs vides (sinon plantage)*/
				    		if (!vals[j].equals("")){
				    			aucuneValeur = false;
				    		    if (!created){
				    		    	/*Création de l'attribut*/
					    			a.put(attributes[i],vals[j]);
					    			created=true;
					    		}
					    		else{
					    			/*Ajout d'une valeur*/
					    			a.get(attributes[i]).add(vals[j]);
					    		}
				    		    if (!attributes[i].equals(LDAP_USER_PASSWORD))
				    		    	log.info("LDAPHandler:write() :          + Nouvelle valeur : "+vals[j]);
				    		}

				    	}
					}
					if (aucuneValeur){
						log.info("LDAPHandler:write() :          + Mise à vide");
					}
				}

				try {

					log.info("LDAPHandler:write() : tentative d'enregistrement ...");

					ctx.modifyAttributes(shortDn,DirContext.REPLACE_ATTRIBUTE,a);

					log.info("LDAPHandler:write() : enregistrement effectue.");
	            }
	            catch(NoPermissionException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => NoPermissionException : ",e);
					throw new WriteLDAPException("Vous n'avez pas les droits pour modifier l'un des attributs.");
	            }
	            catch (AttributeModificationException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => AttributeModificationException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
				}
				catch (OperationNotSupportedException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => OperationNotSupportedException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
				}
				catch (PartialResultException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => PartialResultException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
					}
				catch (NamingException e) {
	                log.error("LDAPHandler:write() : modification de "+a+" => NamingException : ",e);
					throw new WriteLDAPException("Une erreur est survene lors de la modification de l'un des champs.");
				}
			}
			else{
			    log.error("LDAPHandler:write() : impossible de trouver l'entree LDAP");
				throw new SearchLDAPException("Erreur dans l'acquisition d'un attribut.");
			}
		}
        }
        }

        public void addEntry( User user ) throws LDAPException{

            if (true){
            try {
                        // obtain initial directory context using the environment
                        if(ctx == null){
                            ctx = new InitialDirContext( env );
                        }
                        // now, create the root context, which is just a subcontext
                        // of this initial directory context.
                        ctx.bind("uid=" + user.getDn() + ","+ base , user);
                } catch ( NameAlreadyBoundException nabe ) {
                        log.error("name already bound" , nabe);
                        throw new LDAPException();
                } catch ( NamingException e ) {
                        log.error("naming exception" , e);
                        throw new LDAPException();
                }
            }
        }

        public void updateEntry( User user ) throws LDAPException{

            if (true){
            try {
                        // obtain initial directory context using the environment
                        if(ctx == null){
                            ctx = new InitialDirContext( env );
                        }

                        ctx.rebind("uid=" + user.getDn() + ","+ base , user);
                } catch ( NameAlreadyBoundException nabe ) {
                        log.error("name already bound" , nabe);
                        throw new LDAPException();
                } catch ( NamingException e ) {
                        log.error("naming exception" , e);
                        throw new LDAPException();
                }
            }
        }
}
